﻿#region Using Directives

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using GeekRule.ORM.Entities;

#endregion

namespace GeekRule.ORM.Query
{
    /// <summary>
    /// Base QueryAdapter housing vendor-agnostic query logic.
    /// </summary>
    public abstract class QueryAdapterBase : IQueryAdapter
    {
        #region Constants

        const string ParamPattern = "@p{0}";

        #endregion

        #region Private Members

        private DbConnection _connection;
        private DbTransaction _transaction;
        private IDataReader _reader;
        private int _readerResult = 0;

        #endregion

        #region Public Properties

        public DbConnection Connection
        {
            get { return _connection; }
            set { _connection = value; }
        }

        public DbTransaction Transaction
        {
            get { return _transaction; }
            set { _transaction = value; }
        }

        #endregion

        #region Constructors

        protected QueryAdapterBase(DbConnection connection)
        {
            if (connection == null)
            {
                throw new ArgumentNullException("connection is null");
            }

            _connection = connection;
        }

        protected QueryAdapterBase(DbConnection connection, DbTransaction transaction)
        {
            if (connection == null)
            {
                throw new ArgumentNullException("connection is null");
            }

            if (transaction == null)
            {
                throw new ArgumentNullException("transaction is null");
            }

            _connection = connection;
            _transaction = transaction;
        }

        #endregion

        #region Public Query Methods

        public abstract DataSet ExecuteDataSet(DbCommand command);

        public int ExecuteNonQuery(DbCommand command)
        {
            PrepareCommand(command);
            return command.ExecuteNonQuery();
        }

        public List<T> ExecuteQuery<T>(DbCommand command) where T : EntityBase
        {
            PrepareCommand(command);

            List<T> entities = new List<T>();

            using (IDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    entities.Add(EntityFactory.GetInstance<T>(reader));
                }

                if (!reader.IsClosed)
                {
                    reader.Close();
                }
            }

            return entities;
        }

        public void ExecuteMultiResultQuery(DbCommand command)
        {
            PrepareCommand(command);

            _reader = command.ExecuteReader();
        }

        public List<T> GetResult<T>() where T : EntityBase
        {
            List<T> entities = new List<T>();

            if (_reader != null)
            {
                if (_readerResult > 0)
                {
                    _reader.NextResult();
                }

                _readerResult++;

                while (_reader.Read())
                {
                    entities.Add(EntityFactory.GetInstance<T>(_reader));
                }
            }
            else
            {
                throw new Exception("Reader is null.  Ensure that ExecuteMultiResultQuery was called first.");
            }

            return entities;
        }

        public IDataReader ExecuteReader(DbCommand command)
        {
            PrepareCommand(command);
            return command.ExecuteReader();
        }

        public object ExecuteScalar(DbCommand command)
        {
            PrepareCommand(command);
            return command.ExecuteScalar();
        }

        #endregion

        #region Public Helper Methods

        public abstract DbCommand GetCommand(CommandType commandType, string commandText, params object[] values);

        #endregion

        #region Protected Methods

        protected DbConnection GetOpenConnection()
        {
            if (_connection == null)
            {
                throw new NullReferenceException("Connection is not set");
            }

            if (_connection.State == ConnectionState.Closed)
            {
                _connection.Open();
            }

            return _connection;
        }

        protected void PrepareCommand(DbCommand command)
        {
            PrepareCommand(command, null);
        }

        protected void PrepareCommand(DbCommand command, DbTransaction transaction)
        {
            if (command.Connection == null || !command.Connection.Equals(_connection))
            {
                command.Connection = GetOpenConnection();
            }

            if (transaction != null)
            {
                command.Transaction = transaction;
            }
        }

        protected void AutoAssignCommandTextCommandValues(DbCommand command, object[] parameterValues)
        {
            if (command.CommandType != CommandType.Text)
            {
                throw new NotSupportedException("AutoAssignCommandTextCommandValues does not support " + command.CommandType + " commands");
            }

            if (parameterValues != null && parameterValues.Length > 0)
            {
                string adjustedQuery = command.CommandText;
                string[] parameters = new string[parameterValues.Length];

                for (int i = 0; i < parameterValues.Length; i++)
                {
                    parameters[i] = string.Format(ParamPattern, i);
                }

                adjustedQuery = string.Format(adjustedQuery, parameters);

                command.CommandText = adjustedQuery;

                for (int i = 0; i < parameterValues.Length; i++)
                {
                    AddParameterByValue(command, parameters[i], (parameterValues[i] ?? DBNull.Value));
                }
            }
        }

        protected abstract void AddParameterByValue(DbCommand command, string parameterName, object value);

        #endregion
    }
}
